Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/phoenixframework/phoenix_live_view/llms.txt

Use this file to discover all available pages before exploring further.

The Phoenix.LiveView.JS module provides commands for executing JavaScript utility operations on the client without writing custom JavaScript code.

Overview

JS commands are DOM-patch aware, meaning operations applied by the JS APIs persist across server updates. They support common client-side needs like:
  • Adding/removing CSS classes
  • Setting/removing attributes
  • Showing/hiding content
  • Animations and transitions
  • Pushing enhanced events to the server

Client Utility Commands

Visibility

Show hidden elements with optional transitions:
<div id="modal" class="hidden">
  My Modal
</div>

<button phx-click={JS.show(to: "#modal", transition: "fade-in")}>
  Show Modal
</button>
Options:
  • :to - DOM selector (default: interacted element)
  • :transition - CSS classes or 3-tuple for animation
  • :time - Transition duration in ms (default: 200)
  • :display - Display value (default: "block")
  • :blocking - Block UI during transition (default: true)

CSS Classes

Add CSS classes to elements:
<button phx-click={JS.add_class("highlight underline", to: "#item")}>
  Highlight
</button>
Options:
  • :to - DOM selector
  • :transition - Animation classes
  • :time - Duration in ms
  • :blocking - Block UI (default: true)

Attributes

Set an attribute on elements:
<button phx-click={JS.set_attribute({"aria-expanded", "true"}, to: "#dropdown")}>
  Expand
</button>
Cannot set DOM properties like input value. Use JS.dispatch/2 with custom events instead.

Transitions

Apply temporary CSS transitions:
<div id="item">My Item</div>
<button phx-click={JS.transition("shake", to: "#item")}>Shake!</button>
3-tuple syntax:
<div phx-mounted={JS.transition({"ease-out duration-300", "opacity-0", "opacity-100"}, time: 300)}>
  Fades in on mount
</div>
The time option should match the duration in your CSS classes for smooth animations.

Focus Management

Send focus to an element:
JS.focus(to: "#search-input")

Enhanced Push Events

Customize server event handling with additional options:
<button phx-click={JS.push("inc", 
                           loading: ".thermo", 
                           target: @myself, 
                           value: %{limit: 40})}>
  +
</button>

Options

  • :target - Selector or component ID (overrides phx-target)
  • :loading - Selector to apply loading classes to
  • :page_loading - Trigger page loading events (default: false)
  • :value - Map of values to send (overrides phx-value-* attributes)
Values from phx-value-* attributes are merged with the :value option, with the option taking precedence.

Dispatching Events

Dispatch custom DOM events:
<button phx-click={JS.dispatch("click", to: ".nav")}>Click Nav</button>

Options

  • :to - DOM selector (default: interacted element)
  • :detail - Map of data available in event.detail
  • :bubbles - Whether event bubbles (default: true)
  • :blocking - Block UI until event.detail.done() is called

Custom Event Example

window.addEventListener("app:clipcopy", (event) => {
  if ("clipboard" in navigator) {
    if (event.target.tagName === "INPUT") {
      navigator.clipboard.writeText(event.target.value)
    } else {
      navigator.clipboard.writeText(event.target.textContent)
    }
  }
})
click events dispatched with JS.dispatch are MouseEvent types and cannot have custom details.

Executing Stored Commands

Execute JS commands from element attributes:
<div id="modal" phx-remove={JS.hide("#modal")}>...</div>
<button phx-click={JS.exec("phx-remove", to: "#modal")}>Close</button>

Composing Commands

Commands can be chained together:
<button phx-click={
  JS.push("modal-closed") 
  |> JS.remove_class("show", to: "#modal", transition: "fade-out")
}>
  Hide Modal
</button>

Extracting to Functions

alias Phoenix.LiveView.JS

def hide_modal(js \\ %JS{}, selector) do
  js
  |> JS.push("modal-closed")
  |> JS.remove_class("show", to: selector, transition: "fade-out")
end
<button phx-click={hide_modal("#modal")}>Hide Modal</button>
Commands execute in order on the client. Fully client-side commands (like hide) execute immediately without waiting for server responses.

DOM Selectors

All commands accept a :to option with these selector types:
Standard CSS selector:
JS.show(to: "#modal")
JS.hide(to: ".notification")
JS.add_class("active", to: "body a:nth-child(2)")
Complete modal with show/hide functionality:
alias Phoenix.LiveView.JS

def hide_modal(js \\ %JS{}) do
  js
  |> JS.hide(transition: "fade-out", to: "#modal")
  |> JS.hide(transition: "fade-out-scale", to: "#modal-content")
end

def modal(assigns) do
  ~H"""
  <div id="modal" class="phx-modal" phx-remove={hide_modal()}>
    <div
      id="modal-content"
      class="phx-modal-content"
      phx-click-away={hide_modal()}
      phx-window-keydown={hide_modal()}
      phx-key="escape"
    >
      <button class="phx-modal-close" phx-click={hide_modal()}></button>
      <p>{@text}</p>
    </div>
  </div>
  """
end

Client-Side JS Execution

Execute JS commands from JavaScript:
// In a client hook
this.js().show(this.el, {transition: "fade-in"})

Encoding Commands

For custom JSON libraries or dynamic execution:
socket
|> push_event("myapp:exec_js", %{
  to: "#items-#{item.id}",
  js: JS.show() |> JS.to_encodable()
})
window.addEventListener("phx:myapp:exec_js", e => {
  const {to, js} = e.detail
  const el = document.querySelector(to)
  if (el && js) {
    window.liveSocket.execJS(el, js)
  }
})
When using Jason or JSON libraries, commands are automatically encoded—no need to call to_encodable/1.

See Also